<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title>文件断点续传</title>
    <link
      type="text/css"
      rel="stylesheet"
      href="./lib/element-ui@2.15.14/index.css"
    />
    <script type="text/javascript" src="./lib/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> -->
    <!-- <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/npm/vue@2"
    ></script> -->

    <script
      type="text/javascript"
      src="./lib/element-ui@2.15.14/index.js"
    ></script>
    <script type="text/javascript" src="./lib/axios.min.js"></script>
    <!-- <script type="text/javascript" src="./lib/crypto-js.min.js"></script> -->
    <script type="text/javascript" src="./lib/spark-md5.min.js"></script>
  </head>
  <body>
    <!-- start -->
    <div id="app">
      <h2>请选择上传文件</h2>
      <el-progress
        v-if="isUploading"
        :percentage="uploadProgress"
        :format="uploadFormat"
      ></el-progress>
      <input class="video" type="file" @change="fileChange" />

      <el-button
        type="submit"
        @click="handleUpload($event, '.video', '/api/upload/upload')"
        :loading="isUploading"
      >
        提交
      </el-button>

      <el-button type="warn" @click="stopUpload">暂停上传</el-button>
    </div>
    <!-- end -->
  </body>

  <!-- js start -->
  <script type="text/javascript">
    Vue.prototype.$message = ELEMENT.Message;
    Vue.prototype.$loading = ELEMENT.Loading.service;

    new Vue({
      el: "#app",
      data() {
        return {
          msg: "Hello World",
          count: 0, // 记录需要上传的文件下标
          fileMd5: null,

          isUploading: false, // 是否正在上传
          uploadProgress: 0,
        };
      },
      components: {},
      watch: {},
      mounted() {},
      methods: {
        /** 上传文件 */
        handleUpload: async function (event, name, url) {
          const _this = this;

          let files = document.querySelector(name).files;

          if (!files.length) {
            return this.$message.error("请选择文件");
          }

          // 默认切片数量
          const sectionLength = 10;

          // 首先请求接口，获取服务器是否存在此文件
          // count为0则是第一次上传，count不为0则服务器存在此文件，返回已上传的切片数
          this.count = (await this.handleCancel(files[0])) || 0;

          let count = this.count;

          // 更新进度
          _this.uploadProgress = 0;

          // 申明存放切片的数组对象
          let fileCurrent = [];
          // 循环file文件对象
          for (const file of [...files]) {
            // 得出每个切片的大小
            let itemSize = Math.ceil(file.size / sectionLength);

            // 循环文件size，文件blob存入数组
            let current = 0;
            for (current; current < file.size; current += itemSize) {
              fileCurrent.push({
                file: file.slice(current, current + itemSize),
              });
            }

            // axios模拟手动取消请求
            const CancelToken = axios.CancelToken;
            const source = CancelToken.source();
            this.source = source;

            // 当断点续传时，处理切片数量，已上传切片则不需要再次请求上传
            fileCurrent =
              count === 0
                ? fileCurrent
                : fileCurrent.slice(count, sectionLength);

            // 循环切片请求接口
            for (const [index, item] of fileCurrent.entries()) {
              let formData = new FormData();
              formData.append("file", item.file); // file为切片blob对象
              formData.append("filename", file.name); // filename为文件名
              formData.append("total", sectionLength); // total为总切片数
              formData.append("index", index + count + 1); // index为当前切片数

              formData.append("totalChunks", sectionLength); // 总切片数
              formData.append("currentChunk", index + count + 1); // 当前切片数
              formData.append("fileHash", this.fileMd5); // 文件hash

              _this.isUploading = true;
              await axios({
                url: `http://localhost:8080${url}?fileHash=${
                  this.fileMd5
                }&index=${index + count + 1}`,
                method: "POST",
                data: formData,
                cancelToken: source.token,
              })
                .then((res) => {
                  if (!res.data.success) {
                    _this.showTipsWord(`<div>上传失败</div>`);
                    console.log(res.data);
                    return false;
                  }

                  // 更新进度
                  _this.uploadProgress = Number(res.data.progress || 0);

                  if (res.data.isFinish) {
                    _this.isUploading = false;
                    _this.showTipsWord(` <div>上传成功~</div>`);
                    return;
                  }

                  // 返回数据显示进度
                  _this.showTipsWord(`进度${res.data.progress}%`);
                })
                .catch((err) => {
                  _this.isUploading = false;
                  console.log(err);
                });
            }
          }
        },

        /** 暂停上传 */
        stopUpload() {
          if (!this.source) {
            this.$message.error("没有文件，点你妈？");
            return console.warn("没有，点你妈？");
          }

          this.source.cancel("取消请求");
        },

        /** 获取文件md5 */
        getFileMd5(file) {
          const _this = this;
          return new Promise((resolve, reject) => {
            const loading = _this.$loading({
              lock: true,
              text: "文件加载中...",
              spinner: "el-icon-loading",
              background: "rgba(0, 0, 0, 0.7)",
            });

            const reader = new FileReader();

            reader.onloadend = function () {
              // 法1
              // const fileData = reader.result;
              // const md5 = CryptoJS.MD5(fileData);
              // resolve(md5.toString());
              // console.log(`[getFileMd5 法1]: ` + md5.toString());

              // 法2
              const arrayBuffer = reader.result;
              const md5Hash = SparkMD5.ArrayBuffer.hash(arrayBuffer);
              resolve(md5Hash);
              console.log(`[getFileMd5 法2]: ` + md5Hash);
              loading.close();
            };

            reader.onerror = function (err) {
              console.error(err);
              loading.close();
              reject(err);
            };

            reader.readAsArrayBuffer(file);
          });
        },

        /** 文件变更 */
        async fileChange(e) {
          console.log(`[fileChange]:`, e);
          console.log(`[e.target.files]`, e.target.files[0]);

          this.fileMd5 = await this.getFileMd5(e.target.files[0]);
          console.log(`[fileChange getFileMd5]`, this.fileMd5);
        },

        /** 展示提示信息 */
        showTipsWord(word = "") {
          document.querySelector("h2").innerHTML = word;
        },

        // 请求接口，查询上传文件是否存在
        // count为0表示不存在，count不为0则已上传对应切片数
        handleCancel: async function (file) {
          const _this = this;
          // const fileHash = await this.getFileMd5(file);
          const fileHash = this.fileMd5;

          return axios({
            method: "get",
            url: "http://localhost:8080/api/upload/check-file",
            headers: { "Content-Type": "application/json; charset = utf-8" },
            params: {
              fileName: file.name,
              fileHash,
              fileSize: file.size,
              fileType: file.type,
              fileLastModified: file.lastModified,
            },
          })
            .then((res) => {
              if (res.data.count === 0) {
                _this.showTipsWord("校验通过，准备上传...");
                return Promise.reject("文件不存在");
              }

              if (res.data.status === "FILE_ALREADY_EXIST") {
                _this.showTipsWord("文件已存在，无需继续上传");
                this.$message.error("文件已存在，无需继续上传");
                return Promise.reject("文件已存在");
              }
              return res.data.count;
            })
            .catch((err) => {
              console.log(err);
              return Promise.reject(err);
            });
        },

        /** 进度条格式化方法 */
        uploadFormat(percentage) {
          return percentage === 100 ? "ok" : `${percentage}%`;
        },
      },
    });
  </script>
</html>
